package santiago.sounds;

import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.FloatControl;

public enum Music {

    Game("game.wav"),
    Menu("menu.wav");
    private static final String FOLDER = "/ressources/music/";
    private static final int FADE_IN_PERIOD = 2500;
    private static final int FADE_OUT_PERIOD = 4000;
    private static Music CurrentlyPlaying = null;
    private boolean Paused = true;
    private Clip Clip;
    private FloatControl Volume;

    Music(String FileName) {
        try {
            Clip = AudioSystem.getClip();
            File f = new File(System.getProperty("user.dir") + FOLDER + FileName);
            Clip.open(AudioSystem.getAudioInputStream(f));
            Volume = (FloatControl) Clip.getControl(FloatControl.Type.MASTER_GAIN);
            Volume.setValue(Volume.getMinimum());
        } catch (Exception ex) {
            Logger.getLogger(Music.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public synchronized void play() {
        if (Paused) {
            //Start the new music
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Clip.setMicrosecondPosition(0);
                    Clip.loop(Clip.LOOP_CONTINUOUSLY);
                    fadeIn();
                }
            }).start();

            //Stop the old playing music
            if (CurrentlyPlaying != null) {
                CurrentlyPlaying.pause();
            }
            CurrentlyPlaying = this;
            Paused = false;
        }
    }

    public synchronized void pause() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (!Paused) {
                    fadeOut();
                    try {
                        Thread.sleep(FADE_OUT_PERIOD);
                    } catch (InterruptedException ex) {
                    }
                    Clip.stop();
                    Paused = true;
                }
            }
        }).start();
    }

    /**
     * Increases the music's volume to it's maximum
     */
    private synchronized void fadeIn() {
        shiftVolume(Volume.getMaximum(), FADE_IN_PERIOD);
    }

    /**
     * Decreases the music's volume to it's minimum
     */
    private synchronized void fadeOut() {
        shiftVolume(Volume.getMinimum(), FADE_OUT_PERIOD);
    }

    /**
     * Shift the volume to the new value in the given duration (in milliseconds)
     *
     * @param TargetDB new value in dB
     * @param Duration transition duration in milliseconds
     */
    private void shiftVolume(final float TargetDB, final int Duration) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int SleepingPeriod = 80;
                int Iteration = Duration / SleepingPeriod;

                float Departure = Volume.getValue();
                float Arrival = TargetDB;

                float Delta;
                byte Direction;
                if (Departure < Arrival) {
                    // Increase volume
                    if (Departure < 0) {
                        Delta = -1 * Departure + Arrival;
                    } else {
                        Delta = Arrival - Departure;
                    }
                    Direction = 1;
                } else {
                    // Decrease volume
                    if (Arrival < 0) {
                        Delta = -1 * Arrival + Departure;
                    } else {
                        Delta = Departure - Arrival;
                    }
                    Direction = -1;
                }

                float LogInit = 1;
                int Init = 0; // log(LogInit)
                float LogTarget = 10;
                int Target = 1; // log(LogTarget)
                float LogStep = (LogTarget - LogInit) / Iteration;
                float LogTmp = Direction == 1 ? LogInit : LogTarget;
                double LogValue;

                for (int i = 0; i < Iteration; i++) {
                    LogValue = Math.log10(LogTmp);
                    LogValue = LogValue < Init ? Init : LogValue > Target ? Target : LogValue;

                    Volume.setValue((float) (Direction == 1 ? Departure + Delta * LogValue : Departure - Delta + Delta * LogValue));

                    LogTmp = Direction == 1 ? LogTmp + LogStep : LogTmp - LogStep;
                    try {
                        Thread.sleep(SleepingPeriod);
                    } catch (InterruptedException ex) {
                    }
                }
                Volume.setValue(TargetDB);
            }
        }).start();
    }
}
